package ltd.flython.dlock.common;

import lombok.extern.slf4j.Slf4j;
import ltd.flython.dlock.redis.RedisLock;
import ltd.flython.dlock.redis.RedisLockHelper;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.concurrent.TimeUnit;

@Slf4j
@Aspect
@Component
public class DistributedLockHandler {
    ExpressionParser parser = new SpelExpressionParser();
    TemplateParserContext templateParserContext = new TemplateParserContext("#{", "}");

    @Autowired
    RedisLockHelper redisLockFactory;

    //匹配方法注解
    @Pointcut("@annotation(ltd.flython.dlock.common.DLocked)")
    public void aroundMethod() {
    }


    @Around(value = "(aroundMethod()) && @annotation(locked)")
    public Object logAround(ProceedingJoinPoint pjp, DLocked locked) throws Throwable{
        String lockName = gainLockName(pjp, locked);

        RedisLock lock = redisLockFactory.getLock(lockName);
        boolean l = lock.tryLock();
        if (!l){
            log.info("上锁失败，lock=[{}]",lockName);
            return null;
        }
        //上锁状态指示
        int locking = 1;

        try{
            return pjp.proceed();
        } catch (Throwable t){
            if (locked.unlockImmediatelyException()){
                locking = lock.unlock();
            }
            throw t;
        } finally {
            if (locking > 0 && locked.unlockDelay() > 0){
                lock.unlockDelayed(locked.unlockDelay(), TimeUnit.MILLISECONDS);
            } else {
                lock.unlock();
            }
        }
    }

    private String gainLockName(ProceedingJoinPoint pjp, DLocked locked) {
        String namePrefix = locked.name();
        if (StringUtils.hasLength(namePrefix)){
            namePrefix = locked.value();
        }
        String key = getLockKey(pjp, locked);
        String lockName = namePrefix + ":" + key;
        return lockName;
    }

    private String getLockKey(ProceedingJoinPoint pjp, DLocked locked) {
        String keySpel = locked.key();
        String[] parameterNames = ((MethodSignature) pjp.getSignature()).getParameterNames();
        Object[] args = pjp.getArgs();
        StandardEvaluationContext elContext = new StandardEvaluationContext();
        for (int i = 0; i < parameterNames.length; i++) {
            elContext.setVariable(parameterNames[i],args[i]);
        }
        String key = parser.parseExpression(keySpel).getValue(elContext,String.class);
        return key;
    }
}
